/*
 * Lucas McDaniel
 * GSoC '11
 * HoneyViz
 * 
 * This class serves are a wrapper around all DataSet access.  The two main 
 * query functions are search and groupby which allow for select and select with
 * group by functionality respectively (terms from SQL).  The main query method
 * goes as follows:
 * 
 * 1)   Select the DataSet(s) or subset of DataSet(s) that meat query criteria.
 * 2)   If the returned lists are not ordered, then order them
 *          Order priority:  name, time, location
 * 3)   Apply group by requirements (if necessary)
 * 4)   Group by time interval selected
 * 5)   Make cumulative (if necessary)
 * 6)   Return
 */

import java.util.*;
import org.w3c.dom.*;

public class HoneyData {
    
    private static ArrayList backup = new ArrayList();
    private static ArrayList locationList = new ArrayList();
    private static ArrayList nameList = new ArrayList();
    
    public HoneyData()
    {}
    
    public static void add(NodeList e)
    {
        for (int inc=0; inc<e.getLength(); inc++)
        {
            Element tmp = (Element)e.item(inc);
            DataElement DE = new DataElement(tmp);
            backup.add(DE);
            if (nameList.size()==0)
            {
                DataSet nameDS = new DataSet(DE.name);
                nameDS.addNew(DE);
                nameList.add(nameDS);
            }
            else
            {
                int index = getNameIndex(0, nameList.size()-1, DE.name);
                int cmpVal = ((DataSet)nameList.get(index)).name.compareTo(DE.name);
                if (cmpVal==0)
                    ((DataSet)nameList.get(index)).addNew(DE);
                else if (cmpVal>0)
                {
                    DataSet nameDS = new DataSet(DE.name);
                    nameDS.addNew(DE);
                    nameList.add(index, nameDS);
                }
                else
                {
                    DataSet nameDS = new DataSet(DE.name);
                    nameDS.addNew(DE);
                    nameList.add(index+1, nameDS);
                }
            }
            if (locationList.size()==0)
            {
                DataSet locationDS = new DataSet(DE.latitude, DE.longitude);
                locationDS.addNew(DE);
                locationList.add(locationDS);
            }
            else
            {
                int index = getLocationIndex(0, locationList.size()-1, DE.latitude, DE.longitude);
                double cmpVal = ((DataSet)locationList.get(index)).latitude - DE.latitude;
                if (cmpVal == 0)
                    cmpVal = ((DataSet)locationList.get(index)).longitude - DE.longitude;
                if (cmpVal == 0)
                    ((DataSet)locationList.get(index)).addNew(DE);
                else if (cmpVal > 0)
                {
                    DataSet nameDS = new DataSet(DE.latitude, DE.longitude);
                    nameDS.addNew(DE);
                    locationList.add(index, nameDS);
                }
                else
                {
                    DataSet nameDS = new DataSet(DE.latitude, DE.longitude);
                    nameDS.addNew(DE);
                    locationList.add(index+1, nameDS);
                }
            }
        }
    }
    
    private static int getNameIndex(int first, int last, String name)
    {
        int mid = (first + last)/2;
        int cmpVal = ((DataSet)nameList.get(mid)).name.compareTo(name);
        if (cmpVal == 0)
            return mid;
        else if (first >= last)
            return mid;
        else if (cmpVal < 0)
            return getNameIndex(mid+1, last, name);
        else
            return getNameIndex(first, mid-1, name);
    }
    
    private static int getLocationIndex(int first, int last, double latitude, double longitude)
    {
        int mid = (first + last)/2;
        double cmpVal = ((DataSet)locationList.get(mid)).latitude - latitude;
        if (cmpVal == 0)
            cmpVal = ((DataSet)locationList.get(mid)).longitude - longitude;
        if (cmpVal == 0)
            return mid;
        else if (first >= last)
            return mid;
        else if (cmpVal < 0)
            return getLocationIndex(mid+1, last, latitude, longitude);
        else
            return getLocationIndex(first, mid-1, latitude, longitude);
    }
    
    public static ArrayList search(String[] names, Date[] dates, double[][]location)
    {
        ArrayList rtnVal = new ArrayList();
        if (names != null)
            for (int inc=0; inc<nameList.size(); inc++)
                rtnVal.addAll(((DataSet)nameList.get(inc)).get(names, dates, location));
        else if (location != null)
            for (int inc=0; inc<locationList.size(); inc++)
                rtnVal.addAll(((DataSet)locationList.get(inc)).get(names, dates, location));
        else
            for (int inc=0; inc<nameList.size(); inc++)
                rtnVal.addAll(((DataSet)nameList.get(inc)).get(names, dates, location));
        if (rtnVal.size()==0)
            return rtnVal;
        rtnVal = groupByTimeInterval(rtnVal, true);
        if (AppletState.isHeat())
            rtnVal = makeHeat(rtnVal);
        return rtnVal;
    }
    
    public static ArrayList groupBy(String[] names, Date[] dates, double[][]location, boolean groupName, boolean groupDates, boolean groupLocation)
    {
        ArrayList rtnVal = new ArrayList();
        if (names != null)
            for (int inc=0; inc<nameList.size(); inc++)
                rtnVal = combineSets(rtnVal, ((DataSet)nameList.get(inc)).groupBy(names, dates, location, groupName, groupDates, groupLocation), groupName, groupDates, groupLocation);
        else if (location != null)
            for (int inc=0; inc<locationList.size(); inc++)
                rtnVal = combineSets(rtnVal, order(((DataSet)locationList.get(inc)).groupBy(names, dates, location, groupName, groupDates, groupLocation), groupName), groupName, groupDates, groupLocation);
        else
            for (int inc=0; inc<nameList.size(); inc++)
                rtnVal = combineSets(rtnVal, order(((DataSet)nameList.get(inc)).groupBy(names, dates, location, groupName, groupDates, groupLocation), groupName), groupName, groupDates, groupLocation);
        if (rtnVal.size()==0)
        {
            return rtnVal;
        }
        rtnVal = groupByTimeInterval(rtnVal, groupDates);
        if (AppletState.isHeat())
            rtnVal = makeHeat(rtnVal);
        return rtnVal;
    }
    
    private static ArrayList order(ArrayList list, boolean groupName)
    {
        ArrayList rtnVal = new ArrayList();
        if (groupName)
        {
            for (int inc=0; inc<list.size(); inc++)
            {
                DataElement DE = (DataElement)list.get(inc);
                boolean found = false;
                for (int inc2=0; inc2<rtnVal.size(); inc2++)
                {
                    DataElement DE2 = (DataElement)rtnVal.get(inc2);
                    int cmpVal = DE2.name.compareTo(DE.name);
                    if (cmpVal == 0)
                    {
                        DE2.count += DE.count;
                        found = true;
                        break;
                    }
                    else if (cmpVal > 0)
                    {
                        rtnVal.add(inc2, DE);
                        found = true;
                        break;
                    }
                }
                if (!found)
                    rtnVal.add(DE);
            }
            return rtnVal;
        }
        for (int inc=0; inc<list.size(); inc++)
        {
            DataElement DE = (DataElement)list.get(inc);
            boolean found = false;
            for (int inc2=0; inc2<rtnVal.size(); inc2++)
            {
                DataElement DE2 = (DataElement)rtnVal.get(inc2);
                int cmpVal = DE2.time.compareTo(DE.time);
                if (cmpVal == 0)
                    cmpVal = DE2.name.compareTo(DE.name);
                if (cmpVal == 0)
                {
                    DE2.count += DE.count;
                    found = true;
                    break;
                }
                else if (cmpVal > 0)
                {
                    rtnVal.add(inc2, DE);
                    found = true;
                    break;
                }
            }
            if (!found)
                rtnVal.add(DE);
        }
        return rtnVal;
    }
    
    private static ArrayList combineSets(ArrayList original, ArrayList append, boolean groupName, boolean groupDates, boolean groupLocation)
    {
        int indexOriginal = 0;
        int indexAppend = 0;
        while (indexOriginal != original.size() && indexAppend != append.size())
        {
            DataElement orig = (DataElement)original.get(indexOriginal);
            DataElement app = (DataElement)append.get(indexAppend);
            int cmpVal = 0;
            if (AppletState.getPrimarySearch()!=0)
                cmpVal = LocationManager.getLocationName(orig).compareTo(LocationManager.getLocationName(app));
            else if (groupName)
                cmpVal = orig.name.compareTo(app.name);
            if (groupDates && cmpVal == 0)
                cmpVal = orig.time.compareTo(app.time);
            if (groupLocation && cmpVal == 0)
            {
                cmpVal = orig.latitude > app.latitude ? 1 : (orig.latitude < app.latitude ? -1 : 0);
                if (cmpVal == 0)
                    cmpVal = orig.longitude > app.longitude ? 1 : (orig.longitude < app.longitude ? -1 : 0);
            }
            if (cmpVal == 0)
            {
                orig.count += app.count;
                indexAppend++;
            }
            else if (cmpVal > 0)
            {
                original.add(indexOriginal, append.get(indexAppend));
                indexAppend++;
            }
            indexOriginal++;
        }
        if (indexAppend == 0)
            original.addAll(append);
        else if (indexAppend != append.size())
            original.addAll(append.subList(indexAppend-1, append.size()));
        return original;
    }
    
    private static ArrayList groupByTimeInterval(ArrayList result, boolean addExtra)
    {
        ArrayList rtnVal = new ArrayList();
        
        int index = 0;
        Calendar cal = Calendar.getInstance();
        Calendar cal2 = Calendar.getInstance();
        
        for (int inc=0; inc<result.size(); inc++)
        {
            DataElement DE = ((DataElement)result.get(inc)).makeNew();
            cal.setTime(DE.time);
            if (AppletState.getTimeInterval() == 1)
                cal.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
            else if (AppletState.getTimeInterval() == 2)
                cal.set(Calendar.DAY_OF_MONTH, 1);
            else if (AppletState.getTimeInterval() == 3)
                cal.set(Calendar.DAY_OF_YEAR, 1);
            DE.time = cal.getTime();
            if (rtnVal.size()!=0)
                if (DE.name.compareTo(((DataElement)rtnVal.get(rtnVal.size()-1)).name)!=0)
                    index = rtnVal.size();
            boolean found = false;
            for (int inc2=index; inc2<rtnVal.size(); inc2++)
            {
                DataElement DE2 = (DataElement)rtnVal.get(inc2);
                if (DE2.name.compareTo(DE.name)==0 && DE2.time.compareTo(DE.time)==0 && DE2.latitude == DE.latitude && DE2.longitude == DE.longitude)
                {
                    found = true;
                    DE2.count += DE.count;
                    break;
                }
            }
            if (!found)
                rtnVal.add(DE.makeNew());
        }
        
        if (rtnVal.size()==0)
            return rtnVal;
        
        Date first = ((DataElement)rtnVal.get(0)).time;
        Date last = first;

        for (int inc=0; inc<rtnVal.size(); inc++)
        {
            Date tmp = ((DataElement)rtnVal.get(inc)).time;
            if (first.after(tmp))
                first = tmp;
            else if (last.before(tmp))
                last = tmp;
        }
        if (first.compareTo(last)==0 && addExtra)
        {
            cal.setTime(first);
            cal2.setTime(last);
            if (AppletState.getTimeInterval() == 0)
            {
                cal.add(Calendar.DATE, -1);
                cal2.add(Calendar.DATE, 1);    
            }
            else if (AppletState.getTimeInterval() == 1)
            {
                cal.add(Calendar.WEEK_OF_YEAR, -1);
                cal2.add(Calendar.WEEK_OF_YEAR, 1);
            }
            else if (AppletState.getTimeInterval() == 2)
            {
                cal.add(Calendar.MONTH, -1);
                cal2.add(Calendar.MONTH, 1);
            }
            else if (AppletState.getTimeInterval() == 3)
            {
                cal.add(Calendar.YEAR, -1);
                cal2.add(Calendar.YEAR, 1);
            }
            first = cal.getTime();
            last = cal2.getTime();
            DataElement tmp = ((DataElement)rtnVal.get(0)).makeNew();
            tmp.time = first;
            tmp.count = 0;
            rtnVal.add(0, tmp);
            tmp = ((DataElement)rtnVal.get(rtnVal.size()-1)).makeNew();
            tmp.time = last;
            tmp.count = 0;
            rtnVal.add(tmp);
        }
        int counter = ((DataElement)rtnVal.get(0)).count;
        String name = ((DataElement)rtnVal.get(0)).name;
        return rtnVal;
    }
    
    private static ArrayList makeHeat(ArrayList tmpList)
    {
        if (tmpList.size()==0)
            return tmpList;
        ArrayList rtnVal = new ArrayList();
        Date last = ((DataElement)tmpList.get(0)).time;
        for (int inc=0; inc<tmpList.size(); inc++)
        {
            if (((DataElement)tmpList.get(inc)).time.after(last))
                last = ((DataElement)tmpList.get(inc)).time;
        }
        Calendar cal = Calendar.getInstance();
        cal.setTime(last);
        if (AppletState.getTimeInterval() == 0)
            cal.add(Calendar.DATE, 1);
        else if (AppletState.getTimeInterval() == 1)
            cal.add(Calendar.WEEK_OF_YEAR, 1);
        else if (AppletState.getTimeInterval() == 2)
            cal.add(Calendar.MONTH, 1);
        else if (AppletState.getTimeInterval() == 3)
            cal.add(Calendar.YEAR, 1);
        last = cal.getTime();
        DataElement padding = ((DataElement)tmpList.get(tmpList.size()-1)).makeNew();
        padding.count = 0;
        padding.name = "";
        padding.time = last;
        tmpList.add(padding);
        for (int inc=0; inc<tmpList.size(); inc++)
        {
            DataElement DE = (DataElement)tmpList.get(inc);
            boolean found = false;
            
            Date current = DE.time;
            String name = "";
            if (AppletState.getPrimarySearch()==0)
                name = DE.name;
            else
                name = LocationManager.getLocationName(DE);
            double latitude = DE.latitude;
            double longitude = DE.longitude;
            int count = DE.count;
            DataElement DE2 = (DataElement)tmpList.get(inc);
            for (int inc2=inc+1; inc2<tmpList.size(); inc2++)
            {
                DE2 = (DataElement)tmpList.get(inc2);
                if (AppletState.getPrimarySearch()==0)
                {
                    if (DE2.name.compareTo(name)!=0)
                        break;
                    else if (DE2.latitude == latitude && DE2.longitude == longitude)
                    {
                        found = true;
                        DE2.count += count;
                        break;
                    }
                }
                else
                {
                    if (LocationManager.getLocationName(DE2).compareTo(name)!=0)
                        break;
                    else if (DE2.latitude == latitude && DE2.longitude == longitude)
                    {
                        found = true;
                        DE2.count += count;
                        break;
                    }
                }
            }
            Date finalDate = found ? DE2.time : last;
            
            while (current.before(finalDate))
            {
                DataElement tmp = new DataElement(name, current, count, latitude, longitude);
                cal.setTime(current);
                if (AppletState.getTimeInterval() == 0)
                    cal.add(Calendar.DATE, 1);
                else if (AppletState.getTimeInterval() == 1)
                    cal.add(Calendar.WEEK_OF_YEAR, 1);
                else if (AppletState.getTimeInterval() == 2)
                    cal.add(Calendar.MONTH, 1);
                else if (AppletState.getTimeInterval() == 3)
                    cal.add(Calendar.YEAR, 1);
                current = cal.getTime();
                rtnVal.add(tmp);
            }
            if (AppletState.getPrimarySearch()==0)
            {
                if (DE.name.compareTo(name)!=0)
                {
                    current = DE.time;
                    name = DE.name;
                    count = DE.count;
                }
                else
                    count += DE.count;
            }
            else
            {
                if (LocationManager.getLocationName(DE).compareTo(name)!=0)
                {
                    current = DE.time;
                    name = LocationManager.getLocationName(DE);
                    count = DE.count;
                }
                else
                    count += DE.count;
            }
        }
        return rtnVal;
    }
}
